﻿
Imports System.Reflection
Imports osi.root.connector
Imports osi.root.constants

Public Class invoker(Of delegate_t)
    Private Shared ReadOnly dt As Type
    Private ReadOnly s As Boolean
    Private ReadOnly mi As MethodInfo
    Private ReadOnly m As delegate_t
    Private ReadOnly t As Type
    Private ReadOnly name As String
    Private ReadOnly preb As Boolean
    Private ReadOnly postb As Boolean

    Shared Sub New()
        assert(GetType(delegate_t).is(GetType([Delegate])))
        dt = GetType(delegate_t)
    End Sub

    Public Sub New(ByVal t As Type,
                   ByVal bindingFlags As BindingFlags,
                   ByVal obj As Object,
                   ByVal name As String)
        If Not t Is Nothing AndAlso Not String.IsNullOrEmpty(name) Then
            Me.t = t
            Me.name = name
            Try
                mi = t.GetMethod(name, bindingFlags)
            Catch ex As Exception
                If Not suppress.invoker_error Then
                    raise_error(error_type.warning,
                                "cannot find method ",
                                name,
                                " in type ",
                                t,
                                ", ex ",
                                ex.Message)
                End If
                Return
            End Try

            If mi Is Nothing Then
                If Not suppress.invoker_error Then
                    raise_error(error_type.warning,
                                "cannot find method ",
                                name,
                                " in type ",
                                t)
                End If
                Return
            End If

            s = mi.IsStatic()
            If Not [static]() AndAlso obj Is Nothing Then
                postb = True
            Else
                preb = create_delegate(obj, m)
            End If
        End If
    End Sub

    Public Sub New(ByVal t As Type, ByVal bindingFlags As BindingFlags, ByVal name As String)
        Me.New(t, bindingFlags, Nothing, name)
    End Sub

    Public Sub New(ByVal t As Type, ByVal name As String)
        Me.New(t, BindingFlags.Static Or BindingFlags.Public, name)
    End Sub

    Public Sub New(ByVal t As Type, ByVal obj As Object, ByVal name As String)
        Me.New(t, BindingFlags.Static Or BindingFlags.Instance Or BindingFlags.Public, obj, name)
    End Sub

    Public Sub New(ByVal obj As Object, ByVal name As String)
        Me.New(obj.GetType(), obj, name)
    End Sub

    Public Sub New(ByVal obj As Object, ByVal bindingFlags As BindingFlags, ByVal name As String)
        Me.New(obj.GetType(), bindingFlags, obj, name)
    End Sub

    Public Sub New(ByVal bindingFlags As BindingFlags, ByVal obj As Object, ByVal name As String)
        Me.New(obj, bindingFlags, name)
    End Sub

    Private Function create_delegate(ByVal obj As Object,
                                     ByRef m As delegate_t) As Boolean
        assert(Not t Is Nothing)
        assert(Not String.IsNullOrEmpty(name))
        assert(Not mi Is Nothing)
        Dim d As [Delegate] = Nothing
        Try
            If [static]() Then
                d = [Delegate].CreateDelegate(dt, mi)
            Else
                If obj Is Nothing Then
                    Return False
                Else
                    d = [Delegate].CreateDelegate(dt, obj, mi)
                End If
            End If
        Catch ex As Exception
            If Not suppress.invoker_error Then
                raise_error(error_type.warning,
                            "cannot create delegate for method ",
                            name,
                            " of type ",
                            t,
                            ", ex ",
                            ex.Message)
            End If
            Return False
        End Try

        If Not cast(Of delegate_t)(d, m) Then
            If Not suppress.invoker_error Then
                raise_error(error_type.warning,
                            "failed to convert method ",
                            name,
                            " in type ",
                            t,
                            " to delegate ",
                            dt)
            End If
            Return False
        End If

        Return True
    End Function

    Public Function valid() As Boolean
        Return Not mi Is Nothing
    End Function

    Public Function [static]() As Boolean
        Return s
    End Function

    Public Function [get]() As delegate_t
        Return +Me
    End Function

    Public Function pre_binding() As Boolean
        Return preb AndAlso assert(valid())
    End Function

    Public Function post_binding() As Boolean
        Return postb AndAlso assert(valid())
    End Function

    Public Function pre_bind(ByRef d As delegate_t) As Boolean
        If pre_binding() Then
            assert(Not m Is Nothing)
            d = m
            Return True
        Else
            Return False
        End If
    End Function

    Public Shared Operator +(ByVal this As invoker(Of delegate_t)) As delegate_t
        assert(Not this Is Nothing)
        Dim o As delegate_t = Nothing
        assert(this.pre_bind(o))
        Return o
    End Operator

    Public Function post_bind(ByVal obj As Object, ByRef d As delegate_t) As Boolean
        Return Not obj Is Nothing AndAlso
               valid() AndAlso
               Not [static]() AndAlso
               post_binding() AndAlso
               create_delegate(obj, d)
    End Function

    Public Shared Operator +(ByVal this As invoker(Of delegate_t), ByVal obj As Object) As delegate_t
        assert(Not this Is Nothing)
        Return this(obj)
    End Operator

    Default Public ReadOnly Property bind(ByVal obj As Object) As delegate_t
        Get
            Dim o As delegate_t = Nothing
            assert(post_bind(obj, o))
            Return o
        End Get
    End Property
End Class
